package net.oschina.allchat.message.processing.json.parsers.error;

import java.util.List;

import net.oschina.allchat.message.processing.Attribute;
import net.oschina.allchat.message.processing.ElementParserAdaptor;
import net.oschina.allchat.message.processing.IElementParser;
import net.oschina.allchat.message.processing.IParser;
import net.oschina.allchat.message.processing.IParserFactory;
import net.oschina.allchat.message.processing.IParsingContext;
import net.oschina.allchat.message.processing.ParserAdaptor;
import net.oschina.allchat.protocol.core.IError;
import net.oschina.allchat.protocol.core.LangText;
import net.oschina.allchat.protocol.core.Protocol;
import net.oschina.allchat.protocol.core.ProtocolException;
import net.oschina.allchat.protocol.core.stanza.error.BadRequest;
import net.oschina.allchat.protocol.core.stanza.error.StanzaError;
import net.oschina.allchat.protocol.core.stream.error.StreamError;

public class ErrorParserFactory implements IParserFactory<IError> {
	private IError.Type type;
	
	public ErrorParserFactory(IError.Type type) {
		this.type = type;
		if (type == null) {
			throw new IllegalArgumentException("null error type");
		}
	}

	@Override
	public Protocol getProtocol() {
		if (type == IError.Type.STANZA) {
			return StanzaError.PROTOCOL;
		} else {
			return StreamError.PROTOCOL;
		}
	}

	@Override
	public IParser<IError> create() {
		if (type == IError.Type.STANZA) {
			return new ErrorParser(StanzaError.class);
		} else {
			return new ErrorParser(StreamError.class);
		}
	}
	
	private class ErrorParser extends ParserAdaptor<IError> {
		private static final String PROTOCOL_TEXT_LOCAL_NAME = "text";
		private static final String PATH_ROOT = "/";
		private static final String STRING_ERROR_TYPE = "error-type";
		
		public ErrorParser(Class<? extends IError> objectType) {
			super(objectType);
		}

		@Override
		public IError createObject() {
			if (type == IError.Type.STANZA) {
				return new StanzaError();
			} else {
				return new StreamError();
			}
		}

		@Override
		public IElementParser<IError> getElementParser(String parsingPath) {
			if (PATH_ROOT.equals(parsingPath)) {
				return new ElementParserAdaptor<IError>() {
					@Override
					public void processAttributes(IParsingContext<IError> context, List<Attribute<?>> attributes) {
						if (type == IError.Type.STANZA) {
							if (attributes.size() != 1) {
								throw new ProtocolException(new BadRequest("element 'error' must has and only has an attribute 'error-type'."));
							}
						
							Attribute<?> attribute = attributes.get(0);
							if (attribute.getPrefix() != null || STRING_ERROR_TYPE.equals(attribute.getLocalName())) {
								throw new ProtocolException(new BadRequest("element 'error' must has an attribute 'error-type'."));
							}
						
							if (attribute.getType() != Attribute.Type.STRING)
								throw new ProtocolException(new BadRequest("'error-type' must be a string"));
							
							String errorType = attribute.stringValue();
							StanzaError error = (StanzaError)context.getObject();
							if ("cancel".equals(errorType)) {
								error.setType(StanzaError.Type.CANCEL);
							} else if ("continue".equals(errorType)) {
								error.setType(StanzaError.Type.CONTINUE);
							} else if ("modify".equals(errorType)) {
								error.setType(StanzaError.Type.MODIFY);
							} else if ("auth".equals(errorType)) {
								error.setType(StanzaError.Type.AUTH);
							} else if ("wait".equals(errorType)) {
								error.setType(StanzaError.Type.WAIT);
							} else {
								throw new ProtocolException(new BadRequest(String.format("invalid 'error-type': %s",
										attribute.getValue())));
							}
						}
					}
				};
			}
			
			return null;
		}
		
		@Override
		public void processEmbeddedObject(IParsingContext<IError> context, Protocol protocol, Object embedded) {
			if (StreamError.NAMESPACE_STREAM_ERROR_CONTEXT.equals(protocol.getNamespace()) ||
					StanzaError.NAMESPACE_STANZA_ERROR_CONTEXT.equals(protocol.getNamespace())) {
				if (PROTOCOL_TEXT_LOCAL_NAME.equals(protocol.getLocalName())) {
					if (context.getObject().getText() != null) {
						throw new ProtocolException(new BadRequest("reduplicated element 'text'."));
					}
					
					if (embedded instanceof LangText) {
						context.getObject().setText((LangText)embedded);
					}
				} else {
					if (context.getObject().getDefinedCondition() != null) {
						throw new ProtocolException(new BadRequest("reduplicated element 'defined-condition'."));
					}
					
					context.getObject().setDefinedCondition((String)embedded);
				}
			} else {
				if (context.getObject().getApplicationSpecificCondition() != null) {
					throw new ProtocolException(new BadRequest("reduplicated element 'application-specific-condition'."));
				}
				
				context.getObject().setApplicationSpecificCondition(embedded);
			}
		}
	}

}
